package org.rockwalrus.ruly;

import static org.rockwalrus.ruly.Time.TIME_ZONE;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.google.appengine.api.labs.taskqueue.QueueFactory;
import com.google.appengine.api.labs.taskqueue.TaskOptions;
import com.google.appengine.repackaged.com.google.common.collect.ImmutableMap;
import com.google.wave.api.AbstractRobot;
import com.google.wave.api.Annotation;
import com.google.wave.api.Blip;
import com.google.wave.api.BlipContentRefs;
import com.google.wave.api.Context;
import com.google.wave.api.ElementType;
import com.google.wave.api.Gadget;
import com.google.wave.api.Wavelet;
import com.google.wave.api.event.DocumentChangedEvent;
import com.google.wave.api.event.GadgetStateChangedEvent;
import com.google.wave.api.event.WaveletSelfAddedEvent;
import com.google.wave.api.impl.DocumentModifyAction.BundledAnnotation;

@SuppressWarnings("serial")
public class RulyServlet extends AbstractRobot {
    private static final Logger log = Logger.getLogger(RulyServlet.class.getName());

    
    private static final int VOTING_PERIOD = 48;
    private static final Handler[] handlers = new Handler[] {
	new Handler("proposal:", "PROPOSAL:", "proposal"),
	new Handler("judgement:", "JUDGEMENT REQUEST:", "judgement request"),
	new ParentBasedHandler("patch:", "PATCH PROPOSAL:", "patch proposal")
    };
    
    public RulyServlet() {
        setupOAuth("587679099991", "0O4X5YJTVvw3FZXI5K2gT6NN", "http://www-opensocial.googleusercontent.com/api/rpc");
        setupOAuth("587679099991", "0O4X5YJTVvw3FZXI5K2gT6NN", "http://www-opensocial-sandbox.googleusercontent.com/api/rpc");
        
        setAllowUnsignedRequests(true);
    }
    
    @Override
    public void onWaveletSelfAdded(WaveletSelfAddedEvent event) {
        event.getWavelet().reply("\nShortcut for creating a proposal: start the blip with \"proposal:\"\n"
                                 + "Shortcut for creating a judgement request: start the blip with \"judgement:\"\n"
                                 + "Shortcut for creating a patch proposal: start the blip with \"patch:\"");
    }
    
    @Override
    @Capability(contexts=Context.ALL)
    public void onDocumentChanged(DocumentChangedEvent event) {
        SimpleDateFormat f = new SimpleDateFormat("'on' EEEE, MMMM d, yyyy 'at' h:mm a zzzz.");
        f.setTimeZone(TIME_ZONE);        
        Blip document = event.getBlip();
        String text = document.getContent();

        for (Handler handler : handlers) {
            if (handler.isTriggered(text)) {
                handler.handleBlip(f, event, document);
                break;
            }
        }
    }
    
    @Override
    @Capability(contexts=Context.SELF)
    public void onGadgetStateChanged(GadgetStateChangedEvent event) {
        BlipContentRefs gadgetIndex = event.getBlip().at(event.getIndex());
        if ("http://nomic-wave.googlecode.com/svn/trunk/gadgets/grazing/grazing.xml".equals(((Gadget) gadgetIndex.value()).getUrl()) ) {
            gadgetIndex.updateElement(ImmutableMap.of(event.getModifiedBy() + ":grazeEnd", String.valueOf(event.getTimestamp() + 60000)));
            
            QueueFactory.getDefaultQueue().add(TaskOptions.Builder.url("/tasks/graze")
                                               .param("username", event.getModifiedBy())
                                               .param("startTime", String.valueOf(event.getTimestamp()))
                                               .param("wave", event.getWavelet().getWaveId().serialise())
                                               .param("wavelet", event.getWavelet().getWaveletId().serialise())
                                               .etaMillis(event.getTimestamp() + 60000));
            
//            Wavelet testWavelet = blindWavelet(event.getWavelet().getWaveId(), event.getWavelet().getWaveletId());
//            testWavelet.reply("\nLooks like " +  event.getModifiedBy() + "is chowing down!");
//            testWavelet.submitWith(event.getWavelet());
//            try {
//                this.submit(testWavelet, "http://www-opensocial-sandbox.googleusercontent.com/api/rpc");
//            } catch (IOException e) {
//                log.log(Level.SEVERE, "Couldn't submit wavelet", e);
//            }
        }
    }

    @Override
    protected String getRobotName() {
        return "Ruly (test version)";
    }

    private static class Handler {
	protected static final String VOTING_GADGET = "http://nomic-wave.googlecode.com/svn/branches/production/gadgets/voting/voting.xml";
	private final String keyword;
	private final String replacement;
	private final String kind;
	
	public Handler(String keyword, String replacement, String kind) {
	    super();
	    this.keyword = keyword;
	    this.replacement = replacement;
	    this.kind = kind;
	}

	public boolean isTriggered(String text) {
	    return text.startsWith("\n" + keyword) && text.length() > keyword.length() + 1;
	}

	public void handleBlip(SimpleDateFormat f, DocumentChangedEvent e, Blip document) {
	    replaceTrigger(document);
	    addTimestamp(f, e, document);
	    
	    long deadline = Time.addGameHours(e.getTimestamp(), VOTING_PERIOD);
	    
	    addVotingGadget(document, deadline);
	    addDeadline(f, document, deadline);
	}

	protected void replaceTrigger(Blip document) {
            document.range(0, keyword.length() + 1)
                .replace(BundledAnnotation.listOf(Annotation.FONT_WEIGHT, "bold"), 
                         "\n" + replacement);
	}

	private void addTimestamp(SimpleDateFormat f, DocumentChangedEvent e, Blip document) {
	    String timestamp = f.format(e.getTimestamp());
	    document.append("\n\nThis " + kind + " was made " + timestamp);
	}
	
        private void addDeadline(SimpleDateFormat f, Blip document, long deadline) {
            String timestamp = f.format(deadline);
            document.append("\nVoting ends " + timestamp);
        }

	protected void addVotingGadget(Blip document, long deadline) {
	    addVotingGadget(document, String.valueOf(deadline));
	}

	protected void addVotingGadget(Blip document, String deadline) {
	    Gadget gadget = new Gadget(VOTING_GADGET);
	    
	    gadget.setProperty("deadline", deadline);
	    document.append(gadget);
	}
    }
    
    private static class ParentBasedHandler extends Handler {

        public ParentBasedHandler(String keyword,
                                  String replacement,
                                  String kind) {
            super(keyword, replacement, kind);
        }

	@Override
	public void handleBlip(SimpleDateFormat f, DocumentChangedEvent e, Blip document) {
	    replaceTrigger(document);
	    
	    Blip b = e.getBlip();
	    BlipContentRefs c;

            do {
                b = b.getParentBlip();

                c = b.first(ElementType.GADGET,
                            Gadget.restrictByUrl(VOTING_GADGET));
	    } while (c.isEmpty() && !b.isRoot());
            
            Gadget gadget = (Gadget) c.value();
	    String deadline = null;
	    
	    if (gadget != null)
		deadline = gadget.getProperty("deadline");
	    
	    if (deadline == null)
                deadline = String.valueOf(Time.addGameHours(e.getTimestamp(), VOTING_PERIOD));
	    
	    addVotingGadget(document, deadline);
	}
    }
}